/* 
 * File:   Eval.cpp
 * Author: Gab'z
 * 
 * Created on 1 juin 2012, 21:51
 */

#include "Eval.h"
#include "../../memento/ContextMemento.h"
#include "../../../exception/MementoException.h"
#include "../../Context.h"
#include "../../command/literal/Number.h"
#include "../../command/literal/Expression.h"

namespace calculator {

    Logger* Eval::log = LoggerManager::getInstance()->getLogger("EVAL");

    Eval::Eval(const std::string name) : UndoableRedoableCommand(name) {
    }

    Eval::Eval(const Eval& orig) : UndoableRedoableCommand(orig._name) {
    }

    Eval::~Eval() {
    }

    std::string Eval::isExecutable() const {
        return "";
    }

    Eval* Eval::clone() const {
        return new Eval(_name);
    }

    const Memento* Eval::createMemento() const throw (CommandException) {
         return new ContextMemento(
                clone(),
                Context::getInstance()->getParameters()->clone(),
                Context::getInstance()->getStackList()->clone(),
                Context::getInstance()->getStackList()->getCurrentStackIndex());
    }

    void Eval::restoreFromMemento(const Memento* memento) const throw (MementoException) {
        const ContextMemento* contextMemento = dynamic_cast<const ContextMemento*> (memento);
        if (contextMemento == 0) {
            std::string str = "Bad Memento type, expected : ContextMemento, was ";
            str += typeid (*memento).name();
            throw MementoException(str);
        }
        Context::getInstance()->setParameters(contextMemento->_parameters->clone());
        Context::getInstance()->setStackList(contextMemento->_stackList);
        Context::getInstance()->setCurrentStack(contextMemento->_currentStackIndex);
    }

    /**
     * Execute le premier element de la pile
     * Number -> copie (number.execute())
     * Expression -> boucle qui execute le contenu de l'expression
     * 
     */
    void Eval::execute() const throw (CommandException) {
        log->trace("execute...");
        const Constant* constant = Context::getInstance()->getCurrentStack()->top();
        if (constant != 0) {
            // on execute le premier élément de la pile
            // il faut pourvoir arrêter le processus lorsqu'une erreur a lieu
            const Number* number = dynamic_cast<const Number*> (constant);
            if (number != 0) {
                log->trace("execute... number");
                try {
                    number->execute();
                } catch (CommandException& e) {
                    throw;
                }

            } else {
                const Expression* expression = dynamic_cast<const Expression*> (constant);
                if (expression != 0) {
                    log->trace("execute... expression");
                    // il s'agit d'une expression, on commence par la copier et on dépile
                    Expression* expCopy = expression->clone();
                    Context::getInstance()->getCurrentStack()->pop();
                    // si erreur pendant l'évaluation : place le restant de l'expression dans la ligne de saisie
                    // si succès recopie l'intégralité de l'expression dans la ligne de saisie
                    // dans les 2 cas l'expression ne sera plus dans la pile
                    while (!expCopy->empty()) {
                        log->trace("execute... command found in expression");
                        const Command* commandExp = expCopy->getFirstCommand();

                        // exécuter les commandes contenues dans l'expression, "de gauche à droite"
                        try {
                            std::ostringstream oss;
                            oss << "execute... executing command..." << commandExp->toString();
                            if (commandExp->isExecutable().empty()) {
                                log->trace(oss.str() + "...");
                                commandExp->execute();
                                log->trace(oss.str() + " done");
                            } else {
                                log->error(oss.str() + " not executable command");
                                throw CommandException("Command inside Expression is not executable");
                            }

                        } catch (CommandException& e) {
                            // erreur pendant l'execution d'une commande de l'expression
                            // le restant du contenu de l'expression doit être supprimé
                            std::string str = expCopy->toString();
                            delete expCopy;
                            // on fait remonter le restant de l'expression
                            // pour l'afficher dans la ligne de saisie
                            // fonctionne seulement pour une erreur dans une expression
                            throw CommandException(str);

                        }
                        // commande de l'expression exécutée, on la supprime et on passe à la suivante
                        expCopy->removeFirstCommand();
                    }
                    log->trace("execute... no more command in expression");
                    delete expCopy;
                    // toute l'expression a été évaluée
                    // le résultat se trouve sur la pile
                    // CommandManager doit ensuite afficher l'expression évaluée
                    // en entier dans la ligne de saisie
                    // (fonctionnement normal)
                }
            }
        }
        log->trace("execute done");
    }
}
